#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
#endregion

namespace Xfinity.Rendering.Sas
{
    public delegate void SasUpdateDelegate();
    /// <summary>
    /// This is a game component that implements IUpdateable.
    /// </summary>
    public class SasHost : Microsoft.Xna.Framework.GameComponent
    {
        private ICamera camera;
        private SasHostParameterValues values = new SasHostParameterValues();
        private Dictionary<string, List<EffectParameter>> parameters = new Dictionary<string, List<EffectParameter>>();
        #region EnvironmentMap
        /// <summary>
        /// EnvironmentMap.
        /// </summary>
        public TextureCube EnvironmentMap
        {
            [System.Diagnostics.DebuggerStepThroughAttribute]
            get { return values.EnvironmentMap; }
            [System.Diagnostics.DebuggerStepThroughAttribute]
            set { values.EnvironmentMap = value; }
        }
        #endregion
        #region Effects
        private List<Effect> effects = new List<Effect>();

        /// <summary>
        /// Effects.
        /// </summary>
        public Effect[] Effects
        {
            [System.Diagnostics.DebuggerStepThroughAttribute]
            get { return effects.ToArray(); }
        }
        #endregion
        public void AddEffect(Effect e)
        {
            effects.Add(e);
            BindEffect(e);

        }

        private void BindEffect(Effect e)
        {
            EffectParameter global = e.Parameters.GetParameterBySemantic("SasGlobal");
            if (global != null)
            {
                int[] version = global.GetValueInt32Array(3);
                string versionString = String.Format("{0}.{1}.{2}", version[0], version[1], version[2]);
                if (versionString != "1.0.0")
                {
                    throw new NotSupportedException("DXSAS version is not supported. Only version 1.0.0 is supported. Version is " + versionString);
                }
            }

            foreach (EffectParameter param in e.Parameters)
            {
                EffectAnnotation annotation = param.Annotations["SasBindAddress"];
                if (annotation != null)
                {
                    string address = annotation.GetValueString();
                    switch (address)
                    {
                        case "Sas.Time.Now":
                            {
                                AddUpdateDelegate(UpdateSasTimeNow);
                                break;
                            }
                        case "Sas.Time.Last":
                            {
                                AddUpdateDelegate(UpdateSasTimeLast);
                                break;
                            }
                        case "Sas.Time.FrameNumber":
                            {
                                AddUpdateDelegate(UpdateSasTimeFrameNumber);
                                break;
                            }
                        case "Sas.Camera.WorldToView":
                            {
                                AddUpdateDelegate(UpdateSasCameraWorldToView);
                                break;
                            }
                        case "Sas.Camera.Projection":
                            {
                                AddUpdateDelegate(UpdateSasCameraProjection);
                                break;
                            }
                        case "Sas.Camera.NearFarClipping":
                            {
                                AddUpdateDelegate(UpdateSasCameraNearFarClipping);
                                break;
                            }
                        case "Sas.Camera.Position":
                            {
                                AddUpdateDelegate(UpdateSasCameraPosition);
                                break;
                            }
                        case "Sas.NumAmbientLights":
                            {
                                AddUpdateDelegate(UpdateSasNumAmbientLights);
                                break;
                            }
                        case "Sas.NumPointLights":
                            {
                                AddUpdateDelegate(UpdateSasNumPointLights);
                                break;
                            }
                        case "Sas.NumSpotLights":
                            {
                                AddUpdateDelegate(UpdateSasNumSpotLights);
                                break;
                            }
                        case "Sas.NumDirectionalLights":
                            {
                                AddUpdateDelegate(UpdateSasNumDirectionalLights);
                                break;
                            }
                        case "Sas.NumShadows":
                            {
                                AddUpdateDelegate(UpdateSasNumShadows);
                                break;
                            }
                        default:
                            {
                                if (address.StartsWith("Sas.AmbientLight"))
                                {
                                    int position = RetrieveArrayNumber(address);
                                }
                                break;
                            }
                    }
                    if (!parameters.ContainsKey(address))
                    {
                        parameters.Add(address, new List<EffectParameter>());
                    }
                    parameters[address].Add(param);
                }
            }
        }
        public void RunScript(EffectTechnique t)
        {
            foreach (EffectPass pass in t.Passes)
            {
                foreach (EffectAnnotation annotation in pass.Annotations)
                {
                    if (annotation.Name== "Script")
                    {
                        string originalScript = annotation.GetValueString();
                        string[] commands = originalScript.Split(';');
                        foreach (string command in commands)
                        {
                            
                        }
                    }
                }
            }
        }
        private static int RetrieveArrayNumber(string address)
        {
            int openArray = address.IndexOf('[');
            int closeArray = address.IndexOf(']');
            return int.Parse(address.Substring(openArray+1, closeArray - openArray));
        }

        private void AddUpdateDelegate(SasUpdateDelegate d)
        {
            List<Delegate> list = new List<Delegate>(UpdateDelegate.GetInvocationList());
            if (!list.Contains(d))
            {
                UpdateDelegate += d;
            }
        }

        public SasHost(Game game)
            : base(game)
        {
            UpdateOrder = int.MaxValue;
        }


        /// <summary>
        /// Allows the game component to perform any initialization it needs to before starting
        /// to run.  This is where it can query for any required services and load content.
        /// </summary>
        public override void Initialize()
        {
            camera = ((ICameraHolder)Game.Services.GetService(typeof(ICameraHolder))).ActiveCamera;

            base.Initialize();
        }


        private SasUpdateDelegate UpdateDelegate = delegate { };
        GameTime gameTime;
        /// <summary>
        /// Allows the game component to update itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        public override void Update(GameTime gameTime)
        {
            this.gameTime = gameTime;
            UpdateDelegate();
            base.Update(gameTime);
        }        

        #region Update delegates
        private void UpdateSasNumShadows()
        {
            foreach (EffectParameter param in parameters["Sas.NumShadows"])
            {
                param.SetValue(values.Shadow.Length);
            }
        }

        private void UpdateSasNumDirectionalLights()
        {
            foreach (EffectParameter param in parameters["Sas.NumDirectionalLights"])
            {
                param.SetValue(values.DirectionalLight.Length);
            }
        }

        private void UpdateSasNumSpotLights()
        {
            foreach (EffectParameter param in parameters["Sas.NumSpotLights"])
            {
                param.SetValue(values.SpotLight.Length);
            }
        }

        private void UpdateSasNumPointLights()
        {
            foreach (EffectParameter param in parameters["Sas.NumPointLights"])
            {
                param.SetValue(values.PointLight.Length);
            }
        }

        private void UpdateSasNumAmbientLights()
        {
            foreach (EffectParameter param in parameters["Sas.NumAmbientLights"])
            {
                param.SetValue(values.AmbientLight.Length);
            }
        }

        private void UpdateSasCameraPosition()
        {
            values.Camera.Position = camera.Position;
            foreach (EffectParameter param in parameters["Sas.Camera.Position"])
            {
                param.SetValue(values.Camera.Position);
            }
        }

        private void UpdateSasCameraNearFarClipping()
        {
            UpdateSasCameraNearClipping();
            UpdateSasCameraFarClipping();
        }

        private void UpdateSasCameraFarClipping()
        {
            values.Camera.FarClipping = camera.FarPlane;
            foreach (EffectParameter param in parameters["Sas.Camera.FarClipping"])
            {
                param.SetValue(values.Camera.FarClipping);
            }
        }

        private void UpdateSasCameraNearClipping()
        {
            values.Camera.NearClipping = camera.NearPlane;
            foreach (EffectParameter param in parameters["Sas.Camera.NearClipping"])
            {
                param.SetValue(values.Camera.NearClipping);
            }
        }

        private void UpdateSasCameraProjection()
        {
            values.Camera.Projection = camera.Projection;
            foreach (EffectParameter param in parameters["Sas.Camera.Projection"])
            {
                param.SetValue(values.Camera.Projection);
            }
        }

        private void UpdateSasCameraWorldToView()
        {
            Matrix world = camera.World;
            Matrix view = camera.View;
            Matrix.Multiply(ref world, ref view, out values.Camera.WorldToView);
            foreach (EffectParameter param in parameters["Sas.Camera.WorldToView"])
            {
                param.SetValue(values.Camera.WorldToView);
            }
        }

        private void UpdateSasTimeFrameNumber()
        {
            values.Time.FrameNumber++;
            foreach (EffectParameter param in parameters["Sas.Time.FrameNumber"])
            {
                param.SetValue(values.Time.FrameNumber);
            }
        }

        private void UpdateSasTimeLast()
        {
            values.Time.Last = values.Time.Now;
            foreach (EffectParameter param in parameters["Sas.Time.Last"])
            {
                param.SetValue(values.Time.Last);
            }
        }

        private void UpdateSasTimeNow()
        {
            values.Time.Now = (float)gameTime.TotalRealTime.TotalMilliseconds;
            foreach (EffectParameter param in parameters["Sas.Time.Now"])
            {
                param.SetValue(values.Time.Now);
            }
        }
        #endregion
    }
}